/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @author Mykhailo Stadnyk <mikhus@gmail.com>
 * @author Mykhailo Marchuk <jerry@mirohost.net>
 * @version 0.1
 */
#include "apr_general.h"
#include "apr_lib.h"
#include "apr_buckets.h"
#include "apr_strings.h"
#include "ap_config.h"
#include "util_filter.h"
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_main.h"
#include "util_script.h"
#include "http_core.h"
#include <string.h>
#include <stdio.h>
#include <ctype.h> 
#include <sys/stat.h>

/**
 * Configuration record
 */
typedef struct hitlog_cfg {
    char *hitlog_code_path;
    char *hitlog_html_tag;
    int hitlog_enabled;
    int hitlog_compression;
} hitlog_cfg;

/**
 * Configuring filter state
 */
typedef struct filter_state {
    apr_bucket_brigade *bb;
    int fstate;
} filter_state;

/**
 * Declare ourselves so the configuration routines can find and know us.
 * We'll fill it in at the end of the module.
 */
module AP_MODULE_DECLARE_DATA hitlog_module;

/**
 * Locate directory configuration record for current module.
 */
static hitlog_cfg *hitlog_config( const request_rec *r) {
    return (hitlog_cfg *) ap_get_module_config( r->per_dir_config, &hitlog_module);
}

/**
 * Locate server configuration record for current module.
 */
static hitlog_cfg *hitlog_sconfig( const server_rec *s) {
    return (hitlog_cfg *) ap_get_module_config( s->module_config, &hitlog_module);
}

/**
 * Initialized config defaults if there was no config directives defined for current request
 */
static void *hitlog_create_dir_config( apr_pool_t *p, char *dirspec) {
    hitlog_cfg *cfg;

    /**
     * Allocate the space for our record from the pool supplied.
     */
    cfg = (hitlog_cfg *) apr_pcalloc( p, sizeof( hitlog_cfg));

    /**
     * Now fill in the defaults.  If there are any `parent' configuration
     * records, they'll get merged as part of a separate callback.
     */
    cfg->hitlog_code_path   = "";
    cfg->hitlog_enabled     = 0;
    cfg->hitlog_compression = 0;
    cfg->hitlog_html_tag    = "<body*>";

    return (void *) cfg;
}

/**
 * This function gets called to create a per-server configuration
 * record.  It will always be called for the "default" server.
 *
 * The return value is a pointer to the created module-specific
 * structure.
 */
static void *hitlog_create_server_config( apr_pool_t *p, server_rec *s) {
    hitlog_cfg *cfg;

    /**
     * Allocate the space for our record from the pool supplied.
     */
    cfg = (hitlog_cfg *) apr_pcalloc( p, sizeof( hitlog_cfg));

    /**
     * Now fill in the defaults.  If there are any `parent' configuration
     * records, they'll get merged as part of a separate callback.
     */
    cfg->hitlog_code_path   = "";
    cfg->hitlog_enabled     = 0;
    cfg->hitlog_compression = 0;
    cfg->hitlog_html_tag    = "<body*>";

    return (void *) cfg;
}

/**
 * Defining module commands
 * Current available module commands are:
 * - HitlogEnabled      - turns output processing On/Off
 * - HitlogCodePath     - specifying a name of a file where content to append to an output is stored
 * - HitlogCompression  - turns On/Off CLRF whitespaces removal from an appending content
 * - HitlogHtmlTag      - tag to find after which additional content should be applied
 *
 * IMPORTANT NOTES:
 * Sinopsys of an html tag is very simple but has some specific rules:
 *  - you can specify with * char a place where tag allows as much any symbols as needed,
 *    like <body*> - means that between "<body" and ">" can be anything else while ">" will be found
 *    Other examples: <body*bgcolor="red"> - matching any of <body style="font-size:.9em" bgcolor="red"> 
 *    or <body style="padding:0;margin:0;" bgcolor="red">
 * -  Otherwise each tag will be split in a parts first of which contains everything infront of ">" bracket
 *    and a second part will contain the bracket ">". Anything else found btween this parts will be
 *    treated as successfully found tag element
 * -  If there placed a multiple tags on the page which satisfy the rules above it will be selected 
 *    thetag from the first found occurance.
 * -  Default tag to which assertions should be glued is a <body*> tag
 */
static command_rec hitlog_cmds[] = {
    AP_INIT_FLAG(
        "HitlogEnabled",
        ap_set_flag_slot,
        (void *) APR_OFFSETOF( hitlog_cfg, hitlog_enabled),
        OR_OPTIONS,
        "hitlog switcher"
    ),

    AP_INIT_TAKE1(
        "HitlogCodePath",
        ap_set_string_slot,
        (void *) APR_OFFSETOF( hitlog_cfg, hitlog_code_path),
        ACCESS_CONF|RSRC_CONF,
        "hitlog appended code file path"
    ),

    AP_INIT_FLAG(
        "HitlogCompression",
        ap_set_flag_slot,
        (void *) APR_OFFSETOF( hitlog_cfg, hitlog_compression),
        OR_OPTIONS,
        "strip carriage return spaces from a data file"
    ),

    AP_INIT_TAKE1(
        "HitlogHtmlTag",
        ap_set_string_slot,
        (void *) APR_OFFSETOF( hitlog_cfg, hitlog_html_tag),
        OR_OPTIONS,
        "tag to find after which additional content should be applied"
    ),
    {NULL}
};

/**
 * This function appends a string "what" to string "haystack"
 * after "end" needle in the string "haystack" if haystack
 * contains "start" needle and "end" needle and  anything else
 * beetween them.
 */
apr_size_t hitlog_append( const char *haystack, const char *start, const char *end, char *what, char *result) {
    int i, j;
    size_t spos = 0,
           epos = 0,
           sh   = strlen( haystack),
           ss   = strlen( start),
           se   = strlen( end),
           sw   = strlen( what);

    /**
     * Start scanning the given string "haystack"
     */
    for (i = 0; i < sh; i++) {

        /**
         * begin scanning for start needle in haystack
         */
        if (apr_tolower( haystack[i]) == apr_tolower( start[0]) && spos == 0) {
            spos = i;
            for (j = 1; j < ss; j++) {
                if (apr_tolower( haystack[i+j]) != apr_tolower( start[j])) {
                    spos = 0;
                    break;
                } else {
                    spos = i + j + 1;
                }
            }
            if (spos > 0) {
                i = spos;
            }
        }

        /**
         * scanning haystack for end needle after start needle has been already found (pos > 0)
         */
        if (spos > 0) {
            for (j = 0; j < se; j++) {
                if (apr_tolower( haystack[i+j]) != apr_tolower( end[j])) {
                    epos = 0;
                    break;
                } else {
                    epos = i + j;
                }
            }
            if (epos > 0) {
                /**
                 * Append "what" to "haystack" after "end" is found (epos > 0)
                 */
                 char before[epos + se + 1];
                 char after[sh - epos + se + 1];

                 memset( before, 0, sizeof( before));
                 memset( after, 0, sizeof( after));

                 strncpy( before, haystack, sizeof( before) - 1);
                 strncpy( after, haystack + epos + se, sizeof( after) - 1);

                 strcpy( result, before);

                 strcat( result, what);
                 strcat( result, after);

                 return 1;
            }
        }

    }

    /**
     * No needles found - applying to result original content from "haystack"
     */
    strcpy( result, haystack);
    return 0;
}

/**
 * Append to a filter stack income filter for current module
 */
static void hitlog_in_filter( request_rec *r) {
    ap_add_output_filter( "Hitlog",NULL,r,r->connection);
}

/**
 * This function split needles into chuncks (start, end)
 */
void split_needles( const char *needle, char *start, char *end) {
    int blen, alen, i, sn = strlen( needle), pos = 0, starsplit = 0;

    for (i = 0; i < sn; i++) {
        if (needle[i] == '*') {
           pos       = i;
           starsplit = 1;
           break;
        }
    }

    if (starsplit == 0) {
        pos = sn - 1;
        blen = pos + 1;
        alen = sn -pos + 1;
    } else {
        blen = pos + starsplit;
        alen = sn - pos + starsplit;
    }

    char before[blen];
    char after[alen];

    memset( before, 0, sizeof( before));
    memset( after, 0, sizeof( after));

    strncpy( before, needle, sizeof( before) - 1);
    strncpy( after, needle + pos + starsplit, sizeof( after) - 1);

    strcpy( start, before);
    strcpy( end, after);
}

/**
 * This function implements output filtering.
 * it reads in buckets from brigades and trying to find given tag
 * if tag found it appends a part of content placed in file
 * path to which is determined by HitlogCodePath directive
 * It does nothing if:
 * - HitlogEnablen is not switched on
 * - File with content to append not found
 * - Specified tag not found
 * Also this function trying to compress (obfuscate) file content output
 * depends on directive HitlogCompression value. If it is "on" - filter
 * will compress the output otherwise not.
 */
static apr_status_t hitlog_out_filter( ap_filter_t *f, apr_bucket_brigade *pbbIn) {
    request_rec *r = f->r;
    conn_rec *c = r->connection;
    apr_bucket *pbktIn;
    apr_bucket_brigade *pbbOut;
    hitlog_cfg *cfg = hitlog_config( f->r);

    /**
     * Reading tag from settings and split it into chuncks
     */
    char start_needle [50];
    char end_needle   [50];

    memset( start_needle, 0, sizeof( start_needle));
    memset( end_needle, 0, sizeof( start_needle));

    split_needles( cfg->hitlog_html_tag, start_needle, end_needle);

    /**
     * check if we need to do do any work depends on config directive value (hitlogEnabled is off?)
     */
    if (!cfg->hitlog_enabled) {
        return ap_pass_brigade( f->next, pbbIn);
    }

    FILE *fp;
    char hitlog_code[8000];
    char ch;
    int i = 0;

    int is_compressed = cfg->hitlog_compression;                 /* check if compression is turned on/off */
    memset( hitlog_code, 0, sizeof( hitlog_code));               /* cleanup the init data */

    /**
     * Adds a leading carriage return for appending content if compression is turned off
     * for pretty look output
     */
    if (!is_compressed) {
        i = 1;
        hitlog_code[0] = '\n';
    }

    /* trying to open file with content to append */
    if ((fp = fopen( cfg->hitlog_code_path, "r")) == NULL) {
        return ap_pass_brigade( f->next, pbbIn);
    }

    /* reading file data */
    while (!feof( fp)) {
        ch  = fgetc( fp);
        if (ch != EOF) {
            if (!is_compressed || (is_compressed && (ch != '\n' && ch != '\r'))) {
                hitlog_code[i] = ch;
                i++;
            }
        }
    }

    fclose( fp);  /* closingle and free file pointer */

    /* initializing output bucket variable */
    pbbOut = apr_brigade_create( r->pool, c->bucket_alloc);

    /**
     * Read buckets from bucket brigade
     */
    for (pbktIn = APR_BRIGADE_FIRST( pbbIn); pbktIn != APR_BRIGADE_SENTINEL( pbbIn); pbktIn = APR_BUCKET_NEXT( pbktIn)) {

        const char *data;
        apr_size_t len;
        char *buf;
        apr_size_t res;
        apr_bucket *pbktOut;

        /**
         * If current bucket is an empty metadata backet - do nothing
         */
        if(APR_BUCKET_IS_EOS( pbktIn)) {
            apr_bucket *pbktEOS = apr_bucket_eos_create( c->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL( pbbOut,pbktEOS);
            continue;
        }

        /**
         * Read bucket data
         */
        apr_bucket_read( pbktIn, &data, &len, APR_NONBLOCK_READ);

        /**
         * Write new value of bucket
         */
        buf = apr_bucket_alloc( len + strlen( hitlog_code), c->bucket_alloc);
        memset( buf, 0, sizeof( buf));

        res = hitlog_append( data, start_needle, end_needle, hitlog_code, buf);    /* appending specified file content */
        apr_size_t blen = len + (res > 0 ? strlen( hitlog_code) : 0);  /* calculating new length of bucket */

        /**
         * Appends modified bucket to a bucket brigade
         */
        pbktOut = apr_bucket_heap_create( buf, blen, apr_bucket_free, c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL( pbbOut, pbktOut);
    }

    /* finalizing output */
    apr_brigade_cleanup( pbbIn);
    return ap_pass_brigade( f->next, pbbOut);
}

/**
 * registering module hooks for the server
 */
static void hitlog_register_hooks( apr_pool_t *p) {
    ap_hook_insert_filter( hitlog_in_filter,NULL,NULL,APR_HOOK_MIDDLE);
    ap_register_output_filter( "Hitlog", hitlog_out_filter, NULL, AP_FTYPE_RESOURCE);
}

/**
 * Set up module defaults
 */
module AP_MODULE_DECLARE_DATA hitlog_module = {

    STANDARD20_MODULE_STUFF,
    hitlog_create_dir_config,    /* create per-directory config structure */
    NULL,                        /* merge per-directory config structures */
    hitlog_create_server_config, /* create per-server config structure */
    NULL,                        /* merge per-server config structures */
    hitlog_cmds,                 /* command apr_table_t */
    hitlog_register_hooks        /* register hooks */

};
